[contents] [prev] [next] [top] [bottom] (3 out of 4)

For Loops

In ScriptX, the for loop is very sophisticated and includes

ScriptX has three forms of for loops:

for sources [ conditional ] do expression
for sources [ conditional ] collect [ into collection ]
[ by function ] [ as collectionClass ] expression
for sources [ conditional ] select expression [ into collection ]
[ by function ] [ as collectionClass ] if conditional
In each form of the for expression, sources is the source of iteration and conditional is an additional, optional test that can be used to control the loop.

Simple Iteration

The simplest use of the ScriptX for loop is to execute an expression a specified number of times. This iteration can take three forms:

for numericExpr do expression
for item := rangeOrCollection do expression
for item in rangeOrCollection do expression
In the first form, numericExpr is the number of times to iterate, or an expression that results in a number; and expression is the expression, often a compound expression, to evaluate at every iteration. The expression is evaluated the number of times specified.

for 9 do print "hello" 

allSpaces := #()
for 52 do append allSpaces " "

The last two forms are for iterating over ranges or collections. The item part of the for loop names the local iteration variable, which holds successive values from the range or collection as the loop iterates. This local iteration variable is available within the body of the loop. The rangeOrCollection part is either a range, as described below, or a collection such as an array. Finally, the expression part of the loop, as in the previous form, is an expression, often a compound expression, that is evaluated at each iteration. Note that item := and item in are equivalent forms, and both ranges and collections can be used with either form.

In all of these forms, the for loop returns the last value of the loop body expression when the loop ends.

Here are two examples of using this form of for loop. The sections that follow supply more examples.

global i, myArray := #(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
for i := 1 to 10 do print myArray[i]
global theMin := 25
for i in #(1, 435, 234, 23, 8) do 
	if i < theMin do theMin := i
undefined

The for loop evaluates to undefined because in the last iteration, the if expression evaluates to undefined; theMin, however, has a value of 1, as you would expect.

A ScriptX for expression actually works by creating an object called an iterator. Within a for loop, a script can perform many operations that would be impossible without access to this iterator. Iterators, an advanced topic, are discussed in the "Collections" chapter of the ScriptX Components Guide. Also note that when you want to minimize memory usage and garbage collection, you can avoid creating an iterator by using the forEach method on a collection instead of using a for expression.

for Loops and Ranges

Explicit ranges are specified using the range literal construct. You can also use any expression that yields a Range object.

startValue to
endValue [ by
increment ]
The startValue side of the range is the number from which to start iterating, the endValue side is the number with which to end, and the optional increment is the amount by which to increment at each iteration. If by increment is omitted, the increment is assumed to be 1.

for i := 1 to 23 do print i 
for i in 0 to 100 by 10 do print (i * i)

To decrease the index at each iteration, simply reverse the order of the startvalue and endvalue of the range. For decreasing ranges, you must always state the amount by which to decrement:

for i := 10 to 1 by -1 do print (i + i) 

The range syntax described here is actually a range literal that creates an instance of one of the subclasses of Range. That literal can be used anywhere in a script. Ranges are described in greater detail in the section on "Ranges" beginning on page 153, and in the "Collections" chapter of the ScriptX Components Guide.

for Loops and Collections

You can use for loops to iterate over collections such as arrays and linked lists. At each iteration, each item in the collection in turn is assigned to the iteration variable (i in these examples). The loop body is then evaluated with the local iteration variable set to that value.

for i in #(1,2,3,4) do print i 
for i := #("Francois","Inez","Louis","Margot") do
format debug "%* did it!\n" i @unadorned

Note that ranges, described in the previous section, are also collections, which allows you to form for expressions like this:

global countdown := 10 to 1 by -1
for i in countdown do
(format debug "%* seconds to blast off!\n" i @normal)

Multiple Sources of Iteration

All for loops can have multiple sources of iteration, separated by commas. Each source is iterated at each loop. The loop terminates when any one of the sources of iteration ends.

-- in this example, the first iterator terminates first
for 14, i := 1 to 1000 by 23 do
		(prin i @normal debug; prin " " @unadorned debug)
1 24 47 70 93 116 139 162 185 208 231 254 277 300 OK

-- in the following example, the second iterator terminates first
for 14, i := 1 to 1000 by 230 do
(prin i @normal debug; prin " " @unadorned debug)
1 231 461 691 921 OK

for i := 1 to 10, j := 1 to 10 by 2 do (
	format debug "I is %1. J is %2\n" #(i, j) #(@normal,@normal)
	print (i * j) 
)

-- print only the first 8 elements in the array listOfPeople
for 8, i in listofPeople do print i

Additional Tests

In for expressions you can also specify an additional, optional test to stop each form of the loop from further iterations. Using tests in addition to standard iterations allows both the simplicity of for loops and the flexibility of a repeat loop.

for source while conditional do expression 
for source until conditional do expression
In this form of the for expression, the source part of the loop is any of the forms described in the previous sections, and conditional is an expression that returns false or an object whose value is not false.

x := 1
for 23 while x <= 5 do (
	print x
	x := x + 1
)
1
2
3
4
5
6

You see 1 through 6 in the Listener window because the for expression above prints 1 through 5 and then returns 6, which is the value of the for expression (that is, the value of x, the last expression inside the for expression) at the end of the loop.

Here's the same loop performed using until rather than while:

x := 1
for 23 until x > 5 do (
	print x
	x := x + 1
)
1
2
3
4
5
6

Here's another example using while:



for m := 1 to 100, n := 1 to 100 by 5 while m * n * n
< 10000 do (
	prin m @normal debug; prin ", " @unadorned debug
	prinLn n @normal debug
)

Collecting or Selecting Results

The examples of for loops in the previous sections all used the do reserved word to begin an expression that is evaluated each time the loop iterates. The for loop also has forms that allow you to collect the results of an iterated expression into a collection such as an array, or to collect a particular value of an iterated expression based on a given test.

for sources [ (while|until) conditional ] collect 
[ into collection ] [ by function ] [ as collectionClass ] expression
for sources [ (while|until)conditional ] select expression
[ into collection ] [ by function ] [ as collectionClass ] if conditional
In both forms, source is the source (or sources) of iteration, as described in "Simple Iteration" on page 84 and conditional is the optional while or until test described in "Additional Tests."

The collect form builds a collection (an array by default) of the values of the loop body at each iteration. At each iteration the expression is evaluated, and the values of that expression are collected.

for i := 1 to 10 collect i
#(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

sinvals := for i := 1 to 3 collect sin i
#(0.841470984807897, 0.909297426825682, 0.141120008059867)

The select form allows you to select and assemble a collection of the values of the loop at each iteration based on a given test. If the test returns true, the value of expression at each iteration is put into a collection (an array by default).

Note that to use a complex expression in the expression part of the select form, you must specify that expression inside parentheses. The only expressions that can be used without parentheses in the select form of the for loop are:

Here are some examples:

negativesins := for i := 1 to 10 select (sin i) if (sin i) < 0
#(-0.756802, -0.958924, -0.279415, -0.544021)

global status := #(@doorClosed:false, @keyInIgnition:true, 
@propellerMoving:true)
print status
#(@doorClosed:false, @keyInIgnition:true, @propellerMoving:true)
for i in #(@doorClosed, @keyInIgnition, @propellerMoving) 
	select i if status[i] = true
#(@keyInIgnition, @propellerMoving)

into

The optional into clause allows you to append collected or selected items to an existing collection such as an array:

squares := #()
for s in 1 to 10 collect into squares (s * s)

The into clause is most useful for several disconnected (either sequential or nested) for loops that modify the same collection:

nums := for i in 1 to 5 collect i
for i in 20 to 23 collect into nums i
for i in 40 to 45 collect into nums i
prin nums @complete debug
#(1, 2, 3, 4, 5, 20, 21, 22, 23, 40, 41, 42, 43, 44, 45)

global doors, openDoors := #()
doors := #(@front:false,@back:true,@side:true,@garage:false)
for i in #(@front,@back,@side,@garage)
select i into openDoors if doors[i] = true
#(@back, @side)

Note that the expression following the reserved word into must evaluate to an instance of a collection. That collection expression can be one of the following expressions:

by

The optional by clause in either the collect or select form of the for expression allows you to specify a function that is used to add each new item to the final collection. This function, which is called a collector, can be either a regular function or a generic function that has the required form. To be used as a collector, a function must return the collection into which the items are being collected.

The collector function is called with two arguments on each pass through the loop. The first argument is a collection (specified by the into clause, or created based on the as clause). The second argument is the current value of the loop body. Several generic functions in the Collection protocol have the required form, and others can easily be embodied in a scripted function. The following script joins a sequence of collections into one using merge, a generic function that every Collection object provides a method for.

global firstArray := #(1,2,3,4,5)
global secondArray := #(@yes,@no,@maybe)
global thirdArray := #("sow","ewe","cow","hen")
bigJumbledArray := #(firstArray,secondArray,thirdArray)
niceNeatArray := #()
for i in bigJumbledArray collect into niceNeatArray by merge i
#(1, 2, 3, 4, 5, @yes, @no, @maybe, "sow", "ewe", ...)

By default, a for expression calls the appendReturningSelf global function, which is defined for sequences. This global function is similar to append, except that it returns the collection itself. (By contrast, the generic function append returns the key of the item that was appended.) Another useful function for collecting items is the global function addManyValues.

In the following example, items are collected into a string using a function that is built around prepend, a generic function for which all sequences define a method. This has the effect of reversing the order of a string. (Keep in mind that prepend is an inefficient way to add items to some sequences, including arrays and strings. It is used here only for illustration purposes.) For more information on functions, see Chapter 5, "Functions, Threads and Pipes."

global reagan := new String string:""
speech := "Facts are stupid things"
-- define a function that can act as a collector or selector
function prependReturningSelf sequence value -> (
	prepend sequence value
	return sequence
)
-- use the collector function to reverse the string
for i in speech collect into reagan by prependReturningSelf i
"sgniht diputs era stcaF"

For a variation on this script which uses an anonymous function, see page 101 of Chapter 5, "Functions, Threads and Pipes."

The only types of expressions that can be specified after by without parentheses are the following, where appropriate:

To specify complex expressions in a by clause, you must specify them within parentheses.

as

The optional as clause specifies a collection class into which elements are collected or selected. Collections are described in Chapter 7, "Collections." Note especially the section on strings as collections.

for i in "jabberwocky" collect as Array i
#(106, 97, 98, 98, 101, 114, 119, 111, 99, 107, ...)

for i in "jabberwocky" select i as String if i > 105
"jrwoky"

The first example collects characters in a string literal (a StringConstant object) into an array. They are coerced to Unicode values, which are ImmediateInteger objects, before being added to the array. The second example selects only those elements of a string for which the Unicode value is greater than 105.

The expression following the as reserved word must evaluate to a class. The types of expressions that can be specified after as without parentheses are the same as for the by clause, listed above. To specify a complex expression in an as clause, you must specify it within parentheses.


This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.

Copyright 1996 Apple Computer, Inc. All Rights Reserved.